@@ -398,7 +398,12 @@ def consumer_info_api(request):  | 
            ||
| 398 | 398 | 
                log.has_scan = True  | 
            
| 399 | 399 | 
                log.save()  | 
            
| 400 | 400 | 
                 | 
            
| 401 | 
                - # TODO: 发放会员权益  | 
            |
| 401 | 
                + if not dupload:  | 
            |
| 402 | 
                + user.shots_num += 1  | 
            |
| 403 | 
                + if user.level < UserInfo.MEMBER_BLACK_GOLD:  | 
            |
| 404 | 
                + user.level += 1  | 
            |
| 405 | 
                + user.save()  | 
            |
| 406 | 
                + # TODO: 发放会员权益  | 
            |
| 402 | 407 | 
                 | 
            
| 403 | 408 | 
                return response(200, 'Submit Consumer Info Success', u'提交消费者信息成功')  | 
            
| 404 | 409 | 
                 | 
            
                @@ -6,9 +6,10 @@ from django.conf import settings  | 
            ||
| 6 | 6 | 
                from django.db import transaction  | 
            
| 7 | 7 | 
                from django_logit import logit  | 
            
| 8 | 8 | 
                from django_response import response  | 
            
| 9 | 
                +from paginator import pagination  | 
            |
| 9 | 10 | 
                 | 
            
| 10 | 11 | 
                from account.models import UserInfo  | 
            
| 11 | 
                -from member.models import (GoodsInfo, GoodsOrderInfo, MemberActivityInfo, MemberActivitySigninInfo,  | 
            |
| 12 | 
                +from member.models import (CouponInfo, GoodsInfo, GoodsOrderInfo, MemberActivityInfo, MemberActivitySigninInfo,  | 
            |
| 12 | 13 | 
                MemberActivitySignupInfo, RightInfo)  | 
            
| 13 | 14 | 
                from utils.error.errno_utils import (MemberActivityStatusCode, MemberGoodStatusCode, MemberRightStatusCode,  | 
            
| 14 | 15 | 
                UserStatusCode)  | 
            
                @@ -208,6 +209,23 @@ def good_exchange(request):  | 
            ||
| 208 | 209 | 
                 | 
            
| 209 | 210 | 
                 | 
            
| 210 | 211 | 
                @logit  | 
            
| 212 | 
                +def coupons(request):  | 
            |
| 213 | 
                +    brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID)
               | 
            |
| 214 | 
                +    user_id = request.POST.get('user_id', '')
               | 
            |
| 215 | 
                +    page = request.POST.get('page', 1)
               | 
            |
| 216 | 
                +    num = request.POST.get('num', 20)
               | 
            |
| 217 | 
                +  | 
            |
| 218 | 
                +    coupons = CouponInfo.objects.filter(user_id=user_id, status=True).order_by('-pk')
               | 
            |
| 219 | 
                + coupons, left = pagination(coupons, page, num)  | 
            |
| 220 | 
                + coupons = [coupon.data for coupon in coupons]  | 
            |
| 221 | 
                +  | 
            |
| 222 | 
                +    return response(200, data={
               | 
            |
| 223 | 
                + 'coupons': coupons,  | 
            |
| 224 | 
                + 'left': left,  | 
            |
| 225 | 
                + })  | 
            |
| 226 | 
                +  | 
            |
| 227 | 
                +  | 
            |
| 228 | 
                +@logit  | 
            |
| 211 | 229 | 
                def integrals(request):  | 
            
| 212 | 230 | 
                     brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID)
               | 
            
| 213 | 231 | 
                 | 
            
                @@ -230,15 +248,16 @@ def integrals(request):  | 
            ||
| 230 | 248 | 
                @logit  | 
            
| 231 | 249 | 
                def activity_list(request):  | 
            
| 232 | 250 | 
                     brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID)
               | 
            
| 251 | 
                +    user_id = request.POST.get('user_id', '')
               | 
            |
| 233 | 252 | 
                 | 
            
| 234 | 253 | 
                     raw_activitys = MemberActivityInfo.objects.filter(status=True).order_by('position')
               | 
            
| 235 | 254 | 
                banners = []  | 
            
| 236 | 255 | 
                activitys = []  | 
            
| 237 | 256 | 
                for act in raw_activitys:  | 
            
| 238 | 257 | 
                if act.is_slider:  | 
            
| 239 | 
                - banners.append(act.data)  | 
            |
| 258 | 
                + banners.append(act.data(user_id))  | 
            |
| 240 | 259 | 
                else:  | 
            
| 241 | 
                - activitys.append(act.data)  | 
            |
| 260 | 
                + activitys.append(act.data(user_id))  | 
            |
| 242 | 261 | 
                 | 
            
| 243 | 262 | 
                     return response(200, data={
               | 
            
| 244 | 263 | 
                'banners': banners,  | 
            
                @@ -249,6 +268,7 @@ def activity_list(request):  | 
            ||
| 249 | 268 | 
                @logit  | 
            
| 250 | 269 | 
                def activity_detail(request):  | 
            
| 251 | 270 | 
                     brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID)
               | 
            
| 271 | 
                +    user_id = request.POST.get('user_id', '')
               | 
            |
| 252 | 272 | 
                     activity_id = request.POST.get('activity_id')
               | 
            
| 253 | 273 | 
                 | 
            
| 254 | 274 | 
                try:  | 
            
                @@ -257,7 +277,7 @@ def activity_detail(request):  | 
            ||
| 257 | 277 | 
                return response(MemberActivityStatusCode.ACTIVITY_NOT_FOUND)  | 
            
| 258 | 278 | 
                 | 
            
| 259 | 279 | 
                     return response(200, data={
               | 
            
| 260 | 
                - 'activity': act.data,  | 
            |
| 280 | 
                + 'activity': act.data(user_id),  | 
            |
| 261 | 281 | 
                })  | 
            
| 262 | 282 | 
                 | 
            
| 263 | 283 | 
                 | 
            
                @@ -284,7 +304,7 @@ def activity_signup(request):  | 
            ||
| 284 | 304 | 
                # TODO: 延迟(活动当天)推送模版消息(时间,地点)  | 
            
| 285 | 305 | 
                 | 
            
| 286 | 306 | 
                     return response(200, data={
               | 
            
| 287 | 
                - 'activity': act.data,  | 
            |
| 307 | 
                + 'activity': act.data(user_id),  | 
            |
| 288 | 308 | 
                })  | 
            
| 289 | 309 | 
                 | 
            
| 290 | 310 | 
                 | 
            
                @@ -316,5 +336,5 @@ def activity_signin(request):  | 
            ||
| 316 | 336 | 
                # TODO: 立即推送模版消息(感谢您参加活动,获得的积分)  | 
            
| 317 | 337 | 
                 | 
            
| 318 | 338 | 
                     return response(200, data={
               | 
            
| 319 | 
                - 'activity': act.data,  | 
            |
| 339 | 
                + 'activity': act.data(user_id),  | 
            |
| 320 | 340 | 
                })  | 
            
                @@ -309,6 +309,8 @@ urlpatterns += [  | 
            ||
| 309 | 309 | 
                url(r'^member/good/detail$', member_views.good_detail, name='member_good_detail'),  | 
            
| 310 | 310 | 
                url(r'^member/good/exchange$', member_views.good_exchange, name='member_good_exchange'),  | 
            
| 311 | 311 | 
                 | 
            
| 312 | 
                + url(r'^member/coupons$', member_views.coupons, name='member_coupons'),  | 
            |
| 313 | 
                +  | 
            |
| 312 | 314 | 
                url(r'^member/integrals$', member_views.integrals, name='member_integrals'),  | 
            
| 313 | 315 | 
                 | 
            
| 314 | 316 | 
                url(r'^member/activity/list$', member_views.activity_list, name='member_activity_list'),  | 
            
                @@ -0,0 +1,52 @@  | 
            ||
| 1 | 
                +# -*- coding: utf-8 -*-  | 
            |
| 2 | 
                +# Generated by Django 1.11.26 on 2019-12-12 01:52  | 
            |
| 3 | 
                +from __future__ import unicode_literals  | 
            |
| 4 | 
                +  | 
            |
| 5 | 
                +from django.db import migrations, models  | 
            |
| 6 | 
                +import django_models_ext.fileext  | 
            |
| 7 | 
                +import shortuuidfield.fields  | 
            |
| 8 | 
                +import simditor.fields  | 
            |
| 9 | 
                +  | 
            |
| 10 | 
                +  | 
            |
| 11 | 
                +class Migration(migrations.Migration):  | 
            |
| 12 | 
                +  | 
            |
| 13 | 
                + dependencies = [  | 
            |
| 14 | 
                +        ('member', '0006_auto_20191201_2110'),
               | 
            |
| 15 | 
                + ]  | 
            |
| 16 | 
                +  | 
            |
| 17 | 
                + operations = [  | 
            |
| 18 | 
                + migrations.CreateModel(  | 
            |
| 19 | 
                + name='CouponInfo',  | 
            |
| 20 | 
                + fields=[  | 
            |
| 21 | 
                +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
               | 
            |
| 22 | 
                +                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
               | 
            |
| 23 | 
                +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
               | 
            |
| 24 | 
                +                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
               | 
            |
| 25 | 
                +                ('coupon_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='\u5238\u552f\u4e00\u6807\u8bc6', max_length=22, null=True, unique=True)),
               | 
            |
| 26 | 
                +                ('user_id', models.CharField(blank=True, db_index=True, help_text='\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=32, null=True, verbose_name='user_id')),
               | 
            |
| 27 | 
                +                ('active_at', models.DateTimeField(blank=True, help_text='\u751f\u6548\u65f6\u95f4', null=True, verbose_name='active_at')),
               | 
            |
| 28 | 
                +                ('expire_at', models.DateTimeField(blank=True, help_text='\u8fc7\u671f\u65f6\u95f4', null=True, verbose_name='expire_at')),
               | 
            |
| 29 | 
                +                ('right_id', models.CharField(blank=True, db_index=True, help_text='\u6743\u76ca\u552f\u4e00\u6807\u8bc6', max_length=32, null=True, verbose_name='right_id')),
               | 
            |
| 30 | 
                +                ('right_type', models.IntegerField(choices=[(0, '\u5b9e\u7269'), (1, '\u865a\u62df'), (2, '\u4f18\u60e0\u5238')], db_index=True, default=1, help_text='\u6743\u76ca\u7c7b\u578b', verbose_name='right_type')),
               | 
            |
| 31 | 
                +                ('icon', models.ImageField(blank=True, help_text='\u6743\u76ca\u56fe\u6807', null=True, upload_to=django_models_ext.fileext.upload_path, verbose_name='icon')),
               | 
            |
| 32 | 
                +                ('title', models.CharField(blank=True, help_text='\u6743\u76ca\u540d\u79f0', max_length=255, null=True, verbose_name='title')),
               | 
            |
| 33 | 
                +                ('subtitle', models.CharField(blank=True, help_text='\u6743\u76ca\u4e8c\u7ea7\u540d\u79f0', max_length=255, null=True, verbose_name='subtitle')),
               | 
            |
| 34 | 
                +                ('detail', simditor.fields.RichTextField(blank=True, help_text='\u6743\u76ca\u8be6\u60c5', null=True, verbose_name='detail')),
               | 
            |
| 35 | 
                +                ('level1', models.CharField(blank=True, help_text='level1', max_length=255, null=True, verbose_name='level1')),
               | 
            |
| 36 | 
                +                ('level2', models.CharField(blank=True, help_text='level2', max_length=255, null=True, verbose_name='level2')),
               | 
            |
| 37 | 
                +                ('level3', models.CharField(blank=True, help_text='level3', max_length=255, null=True, verbose_name='level3')),
               | 
            |
| 38 | 
                +                ('level4', models.CharField(blank=True, help_text='level4', max_length=255, null=True, verbose_name='level4')),
               | 
            |
| 39 | 
                +                ('level5', models.CharField(blank=True, help_text='level5', max_length=255, null=True, verbose_name='level5')),
               | 
            |
| 40 | 
                +                ('minlevel', models.IntegerField(default=0, help_text='\u6743\u76ca\u6700\u4f4e\u4f1a\u5458\u7ea7\u522b', verbose_name='minlevel')),
               | 
            |
| 41 | 
                +                ('position', models.IntegerField(db_index=True, default=1, help_text='\u6392\u5e8f', verbose_name='position')),
               | 
            |
| 42 | 
                + ],  | 
            |
| 43 | 
                +            options={
               | 
            |
| 44 | 
                + 'verbose_name': '\u4f1a\u5458\u5238\u4fe1\u606f',  | 
            |
| 45 | 
                + 'verbose_name_plural': '\u4f1a\u5458\u5238\u4fe1\u606f',  | 
            |
| 46 | 
                + },  | 
            |
| 47 | 
                + ),  | 
            |
| 48 | 
                + migrations.AlterModelOptions(  | 
            |
| 49 | 
                + name='membercouponinfo',  | 
            |
| 50 | 
                +            options={'verbose_name': '\u4f1a\u5458\u5238\u4fe1\u606f', 'verbose_name_plural': '\u4f1a\u5458\u5238\u4fe1\u606f'},
               | 
            |
| 51 | 
                + ),  | 
            |
| 52 | 
                + ]  | 
            
                @@ -169,9 +169,83 @@ class RightInfo(BaseModelMixin):  | 
            ||
| 169 | 169 | 
                'level4': self.level4,  | 
            
| 170 | 170 | 
                'level5': self.level5,  | 
            
| 171 | 171 | 
                'minlevel': self.minlevel,  | 
            
| 172 | 
                - "able": True,  | 
            |
| 173 | 
                - "left_num": 3,  | 
            |
| 174 | 
                - "left_tip": 3,  | 
            |
| 172 | 
                + 'able': True,  | 
            |
| 173 | 
                + 'left_num': 3,  | 
            |
| 174 | 
                + 'left_tip': 3,  | 
            |
| 175 | 
                + }  | 
            |
| 176 | 
                +  | 
            |
| 177 | 
                +  | 
            |
| 178 | 
                +class CouponInfo(BaseModelMixin):  | 
            |
| 179 | 
                + PHYSICAL = 0  | 
            |
| 180 | 
                + VIRTUAL = 1  | 
            |
| 181 | 
                + COUPON = 2  | 
            |
| 182 | 
                +  | 
            |
| 183 | 
                + RIGHT_TYPE_TUPLE = (  | 
            |
| 184 | 
                + (PHYSICAL, u'实物'),  | 
            |
| 185 | 
                + (VIRTUAL, u'虚拟'),  | 
            |
| 186 | 
                + (COUPON, u'优惠券'),  | 
            |
| 187 | 
                + )  | 
            |
| 188 | 
                +  | 
            |
| 189 | 
                + coupon_id = ShortUUIDField(_(u'coupon_id'), max_length=32, blank=True, null=True, help_text=u'券唯一标识', db_index=True, unique=True)  | 
            |
| 190 | 
                + user_id = models.CharField(_(u'user_id'), max_length=32, blank=True, null=True, help_text=u'用户唯一标识', db_index=True)  | 
            |
| 191 | 
                +  | 
            |
| 192 | 
                + active_at = models.DateTimeField(_(u'active_at'), blank=True, null=True, help_text=_(u'生效时间'))  | 
            |
| 193 | 
                + expire_at = models.DateTimeField(_(u'expire_at'), blank=True, null=True, help_text=_(u'过期时间'))  | 
            |
| 194 | 
                +  | 
            |
| 195 | 
                + right_id = models.CharField(_(u'right_id'), max_length=32, blank=True, null=True, help_text=u'权益唯一标识', db_index=True)  | 
            |
| 196 | 
                + right_type = models.IntegerField(_(u'right_type'), choices=RIGHT_TYPE_TUPLE, default=VIRTUAL, help_text=u'权益类型', db_index=True)  | 
            |
| 197 | 
                +  | 
            |
| 198 | 
                + icon = models.ImageField(_(u'icon'), upload_to=upload_path, blank=True, null=True, help_text=u'权益图标')  | 
            |
| 199 | 
                + title = models.CharField(_(u'title'), max_length=255, blank=True, null=True, help_text=u'权益名称')  | 
            |
| 200 | 
                + subtitle = models.CharField(_(u'subtitle'), max_length=255, blank=True, null=True, help_text=u'权益二级名称')  | 
            |
| 201 | 
                + detail = RichTextField(_(u'detail'), blank=True, null=True, help_text=u'权益详情')  | 
            |
| 202 | 
                +  | 
            |
| 203 | 
                + level1 = models.CharField(_(u'level1'), max_length=255, blank=True, null=True, help_text=u'level1')  | 
            |
| 204 | 
                + level2 = models.CharField(_(u'level2'), max_length=255, blank=True, null=True, help_text=u'level2')  | 
            |
| 205 | 
                + level3 = models.CharField(_(u'level3'), max_length=255, blank=True, null=True, help_text=u'level3')  | 
            |
| 206 | 
                + level4 = models.CharField(_(u'level4'), max_length=255, blank=True, null=True, help_text=u'level4')  | 
            |
| 207 | 
                + level5 = models.CharField(_(u'level5'), max_length=255, blank=True, null=True, help_text=u'level5')  | 
            |
| 208 | 
                +  | 
            |
| 209 | 
                + minlevel = models.IntegerField(_(u'minlevel'), default=0, help_text=u'权益最低会员级别')  | 
            |
| 210 | 
                +  | 
            |
| 211 | 
                + position = models.IntegerField(_(u'position'), default=1, help_text=u'排序', db_index=True)  | 
            |
| 212 | 
                +  | 
            |
| 213 | 
                + class Meta:  | 
            |
| 214 | 
                + verbose_name = _(u'会员券信息')  | 
            |
| 215 | 
                + verbose_name_plural = _(u'会员券信息')  | 
            |
| 216 | 
                +  | 
            |
| 217 | 
                + def __unicode__(self):  | 
            |
| 218 | 
                + return unicode(self.pk)  | 
            |
| 219 | 
                +  | 
            |
| 220 | 
                + @property  | 
            |
| 221 | 
                + def icon_path(self):  | 
            |
| 222 | 
                + return upload_file_path(self.icon)  | 
            |
| 223 | 
                +  | 
            |
| 224 | 
                + @property  | 
            |
| 225 | 
                + def icon_url(self):  | 
            |
| 226 | 
                + return upload_file_url(self.icon)  | 
            |
| 227 | 
                +  | 
            |
| 228 | 
                + @property  | 
            |
| 229 | 
                + def data(self):  | 
            |
| 230 | 
                +        return {
               | 
            |
| 231 | 
                + 'coupon_id': self.coupon_id,  | 
            |
| 232 | 
                + 'active_at': tc.local_string(self.active_at, format='%Y%m%d'),  | 
            |
| 233 | 
                + 'expire_at': tc.local_string(self.expire_at, format='%Y%m%d'),  | 
            |
| 234 | 
                + 'right_id': self.right_id,  | 
            |
| 235 | 
                + 'right_type': self.right_type,  | 
            |
| 236 | 
                + 'icon': self.icon_url,  | 
            |
| 237 | 
                + 'title': self.title,  | 
            |
| 238 | 
                + 'subtitle': self.subtitle,  | 
            |
| 239 | 
                + 'detail': self.detail,  | 
            |
| 240 | 
                + 'level1': self.level1,  | 
            |
| 241 | 
                + 'level2': self.level2,  | 
            |
| 242 | 
                + 'level3': self.level3,  | 
            |
| 243 | 
                + 'level4': self.level4,  | 
            |
| 244 | 
                + 'level5': self.level5,  | 
            |
| 245 | 
                + 'minlevel': self.minlevel,  | 
            |
| 246 | 
                + 'able': True,  | 
            |
| 247 | 
                + 'left_num': 3,  | 
            |
| 248 | 
                + 'left_tip': 3,  | 
            |
| 175 | 249 | 
                }  | 
            
| 176 | 250 | 
                 | 
            
| 177 | 251 | 
                 | 
            
                @@ -262,13 +336,25 @@ class MemberActivityInfo(BaseModelMixin):  | 
            ||
| 262 | 336 | 
                return upload_file_url(self.slider_image)  | 
            
| 263 | 337 | 
                 | 
            
| 264 | 338 | 
                @property  | 
            
| 265 | 
                - def data(self):  | 
            |
| 339 | 
                + def final_state(self):  | 
            |
| 340 | 
                + tdate = tc.local_date()  | 
            |
| 341 | 
                + if tdate < self.date:  | 
            |
| 342 | 
                + return u'报名中'  | 
            |
| 343 | 
                + if tdate == self.date:  | 
            |
| 344 | 
                + return u'活动中'  | 
            |
| 345 | 
                + return u'已结束'  | 
            |
| 346 | 
                +  | 
            |
| 347 | 
                + def is_signed(self, user_id):  | 
            |
| 348 | 
                + # 是否已报名  | 
            |
| 349 | 
                + return MemberActivitySignupInfo.objects.filter(user_id=user_id, activity_id=self.activity_id, status=True).exists()  | 
            |
| 350 | 
                +  | 
            |
| 351 | 
                + def data(self, user_id):  | 
            |
| 266 | 352 | 
                         return {
               | 
            
| 267 | 353 | 
                'id': self.activity_id,  | 
            
| 268 | 354 | 
                'activity_id': self.activity_id,  | 
            
| 269 | 355 | 
                'title': self.title,  | 
            
| 270 | 356 | 
                'subtitle': self.subtitle,  | 
            
| 271 | 
                - 'date': tc.local_string(self.date, '%Y-%m-%d'),  | 
            |
| 357 | 
                + 'date': tc.local_string(self.date, format='%Y-%m-%d'),  | 
            |
| 272 | 358 | 
                'city': self.city,  | 
            
| 273 | 359 | 
                'location': self.location,  | 
            
| 274 | 360 | 
                'lat': self.lat,  | 
            
                @@ -279,8 +365,8 @@ class MemberActivityInfo(BaseModelMixin):  | 
            ||
| 279 | 365 | 
                'share_img_link': self.share_img_link,  | 
            
| 280 | 366 | 
                'share_h5_link': self.share_h5_link,  | 
            
| 281 | 367 | 
                'slider_image': self.slider_image_url,  | 
            
| 282 | 
                - 'state': 0,  | 
            |
| 283 | 
                - 'is_signed': 0,  | 
            |
| 368 | 
                + 'state': self.final_state,  | 
            |
| 369 | 
                + 'is_signed': self.is_signed(user_id),  | 
            |
| 284 | 370 | 
                }  | 
            
| 285 | 371 | 
                 | 
            
| 286 | 372 | 
                 | 
            
                @@ -1,7 +1,7 @@  | 
            ||
| 1 | 1 | 
                CodeConvert==2.0.5  | 
            
| 2 | 2 | 
                Pillow==5.0.0  | 
            
| 3 | 3 | 
                StatusCode==1.0.0  | 
            
| 4 | 
                -TimeConvert==1.5.0  | 
            |
| 4 | 
                +TimeConvert==1.5.1  | 
            |
| 5 | 5 | 
                furl==2.1.0  | 
            
| 6 | 6 | 
                isoweek==1.3.3  | 
            
| 7 | 7 | 
                jsonfield==2.0.2  |